package com.michael.retail.gateway.config;

import cn.hutool.core.convert.Convert;
import com.michael.retail.commons.constant.AuthConstant;
import com.michael.retail.gateway.util.ResponseUtil;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * 类功能描述:
 * <pre>
 *   资源服务器配置
 * </pre>
 *
 * @author Michael
 * @version 1.0
 * @date 2021/7/7 17:57
 */
@Configuration
@EnableWebFluxSecurity
@ConfigurationProperties(prefix = "security")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ResourcesServerConfig {

    private final ResourcesServerManager resourcesServerManager;

    @Setter
    private List<String> ignoreUrls;

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
                // 配置OAuth 2.0资源服务器支持。
                .oauth2ResourceServer()
                // 启用JWT资源服务器支持
                .jwt()
                //配置转换器以用于将Jwt转换为 abstractaauthenticationtoken
                .jwtAuthenticationConverter(jwtAuthenticationConverter())
        ;

        //自定义处理JWT请求头过期或签名错误的结果
        http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint());

        //对白名单路径，直接移除JWT请求头,避免携带过期token访问白名单出错
        http.addFilterBefore((exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            PathMatcher pathMatcher = new AntPathMatcher();
            //白名单路径移除JWT请求头
            for (String ignoreUrl : ignoreUrls) {
                if (pathMatcher.match(ignoreUrl, path)) {
                    request = exchange.getRequest().mutate().header("Authorization", "").build();
                    exchange = exchange.mutate().request(request).build();
                    return chain.filter(exchange);
                }
            }
            return chain.filter(exchange);
        }, SecurityWebFiltersOrder.AUTHENTICATION);

        // 配置授权
        http.authorizeExchange()
            .pathMatchers(HttpMethod.OPTIONS).permitAll()
            .pathMatchers(Convert.toStrArray(ignoreUrls)).permitAll()
            //鉴权管理器配置
            .anyExchange()
            .access(resourcesServerManager)
            // 异常拦截
            .and().exceptionHandling()
            // 处理未授权异常
            .accessDeniedHandler(accessDeniedHandler())
            // 处理未认证异常
            .authenticationEntryPoint(authenticationEntryPoint())
            .and().csrf().disable()
        ;
        return http.build();
    }

    @Bean
    public ServerAuthenticationEntryPoint authenticationEntryPoint() {
        return (exchange, e) ->
                Mono.defer(() -> Mono.just(exchange.getResponse()))
                    .flatMap(response -> ResponseUtil.writeMsg(response, "无效token"));
    }

    @Bean
    public ServerAccessDeniedHandler accessDeniedHandler() {
        return (exchange, e) ->
                Mono.defer(() -> Mono.just(exchange.getResponse()))
                    .flatMap(response -> ResponseUtil.writeMsg(response, "没有权限"));
    }

    /**
     * @link https://blog.csdn.net/qq_24230139/article/details/105091273
     * ServerHttpSecurity没有将jwt中authorities的负载部分当做Authentication
     * 需要把jwt的Claim中的authorities加入
     * 方案：重新定义权限管理器，默认转换器 JwtGrantedAuthoritiesConverter
     */
    @Bean
    public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedConverter = new JwtGrantedAuthoritiesConverter();
        grantedConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
        grantedConverter.setAuthorityPrefix("");
        grantedConverter.setAuthoritiesClaimName(AuthConstant.JWT_AUTHORITIES_KEY);

        JwtAuthenticationConverter authConverter = new JwtAuthenticationConverter();
        authConverter.setJwtGrantedAuthoritiesConverter(grantedConverter);
        return new ReactiveJwtAuthenticationConverterAdapter(authConverter);
    }
}
